import java.io.*;
import java.util.ArrayList;

public class Parser

{
	private Scanner scanner;
	public Tokens sym; // Stores the current input from getSym()
	private int tabCount;
	private static FileOutputStream outFile;
	private static PrintStream fileData;
    private static SymbolTable symTable;
    private static CodeGen codeGen;
	/* USED FOR INDENTATION & THE PRINTING OF RULE LABELS */

	public void printRule(String ruleName) {

		String ruleOutput = new String("");

		for (int i = 0; i < tabCount; i++) {
			ruleOutput = ruleOutput + "   "; // using 3 space indentation
		}

		ruleOutput = ruleOutput + ruleName;

		fileData.println(ruleOutput);
	}

        /* USED FOR PRINTING TABLE */
        private static void printTable()
        {
            fileData.println("***** Symbol Table Contents *****");
            for(Symbol s : symTable.table)
                fileData.println(s.entryString());
        }

	/* USED FOR PRINTING ERROR INFORMATION */
	private void printError(Tokens symReceived) {
		// For Comparing Output: please use this format as it will be used
		// to compare output results
		fileData.println("Error: SYNTAX: " + symReceived.lexeme.toUpperCase());
                		//System.err.println(" Error Encountered on Line Number: "
		//		+ Scanner.getLineNumber());
                
                printTable();
		System.exit(0);
	}

        private void printError(String errorType) {
		// For Comparing Output: please use this format as it will be used
		// to compare output results
		fileData.println("Error: SEMANTIC: " + errorType);

		//System.err.println(" Error Encountered on Line Number: "
		//		+ Scanner.getLineNumber());
                
                printTable();
		System.exit(0);
	}

        

	/* CONSTRUCTOR */

	public Parser(String args[]) {
		scanner = new Scanner(args[0]);
		codeGen = new CodeGen();
		System.out.println("outfile: " + args[0]);
		try {
			outFile = new FileOutputStream(args[0] + ".out");
			fileData = new PrintStream(outFile);
		}

		catch (IOException e) {
			e.printStackTrace();
			System.err.println("init: Errors accessing output file: " + args[0]
					+ ".out");
			System.exit(-2);
		}

		tabCount = 0;

		sym = Scanner.getSym(); // buffer the symbol input
	}

        /* TEMPORARY SYMBOL FIELDS */
        static ArrayList<String> names;
        String classification = "";
        String type = "";
        String val = "";
        int lexLevel = 0;
        int dim = 0;

        //Use this to clear temporary symbol values.
        public void clear()
        {
            names.clear();
            classification = "";
            type = "";
            val = "";
            dim = 0;
        }

        public String lookupBaseType(Symbol symbol){
            TypeSym t = (TypeSym)symTable.lookUp(symbol.type, "TYPE");
            if(symbol.type.equals("null"))
                return symbol.name;
            while(!t.type.equals("null")){
                t = (TypeSym)symTable.lookUp(t.type, "TYPE");
            }
            return t.name;
        }
	/* PROGRAM FUNCTION */

	private void declarations() {
            if (sym == Tokens.CONST || sym == Tokens.TYPE || sym == Tokens.VAR) {
                    tabCount++;
                    //printRule("declarations");
                    do {
                            if (accept(Tokens.VAR)) {
                                    do {
                                            expect(Tokens.IDENT);
                                            names.add(Scanner.ident);
                                    }
                                    while (accept(Tokens.COMMA));
                                    expect(Tokens.COLON);
                                    newType();
                                    expect(Tokens.SEMI_COLON);
                                    for(String name : names)
                                    {
                                        if(!symTable.add(new Var(name, type, lexLevel, dim)))
                                                printError("REDEFINED SYMBOL");
                                    }

                            } else if (accept(Tokens.CONST)) {
                                    expect(Tokens.IDENT);
                                    names.add(Scanner.ident);
                                    expect(Tokens.ASSIGN_OP);
                                    expect(Tokens.NUMBER);
                                    val = Scanner.number;
                                    expect(Tokens.SEMI_COLON);
                                    if(!symTable.add(new Const(names.get(0), "CONST", "short", lexLevel, val)))
                                    {
                                        printError("REDEFINED SYMBOL");
                                    }
                            } else if (accept(Tokens.TYPE)) {
                                    expect(Tokens.IDENT);
                                    names.add(Scanner.ident);
                                    expect(Tokens.ASSIGN_OP);
                                    newType();
                                    expect(Tokens.SEMI_COLON);
                                    if(!symTable.add(new TypeSym(names.get(0), type, lexLevel, dim)))
                                        printError("REDEFINED SYMBOL");         
                            }
                            clear();
                    } while (sym == Tokens.CONST || sym == Tokens.TYPE
                                    || sym == Tokens.VAR);
                    tabCount--;
            }

	}

	private void newType() {
		if (sym == Tokens.IDENT || sym == Tokens.INT || sym == Tokens.SHORT
				|| sym == Tokens.ARRAY) {
			tabCount++;
			//printRule("newType");
			if (accept(Tokens.ARRAY)) {
				expect(Tokens.NUMBER);
                                dim = Integer.parseInt(Scanner.number);
				expect(Tokens.OF);
				type();
			} else {
				type();
			}
			tabCount--;
		}
	}


	private void procedureDeclarations() {
		if (sym == Tokens.VOID || sym == Tokens.INT || sym == Tokens.IDENT
				|| sym == Tokens.SHORT) {
			do {
				retType();
				expect(Tokens.IDENT);
                                names.add(Scanner.ident);
				expect(Tokens.OPEN_PAREN);
                                ProcSym proc = new ProcSym(names.get(0),type);
                                if(!symTable.add(proc))
                                    printError("REDEFINED SYMBOL");
                                clear();
                                lexLevel = 1;
				procedureFormalParams();
                                proc.setParam(params, isPtr);
				expect(Tokens.CLOSE_PAREN);
                                params.clear();
                                isPtr.clear();

				expect(Tokens.OPEN_BRACE);
				declarations();
				statementSequence();
				expect(Tokens.CLOSE_BRACE);
                                lexLevel = 0;
                                symTable.popTopLexLevel();
                                retType = "";
			} while (sym == Tokens.VOID || sym == Tokens.INT
					|| sym == Tokens.IDENT || sym == Tokens.SHORT);
		}

	}
        ArrayList<String> params = new ArrayList<String>();
        ArrayList<Boolean> isPtr = new ArrayList<Boolean>();

	private void procedureFormalParams() {
		if (sym == Tokens.IDENT || sym == Tokens.INT || sym == Tokens.SHORT) {
			tabCount++;
			//printRule("procedureFormalParams");
			do {
				type();
                                params.add(lookupBaseType(symTable.lookUp(type, "TYPE")));
                                if(!symTable.isDefined(type, "TYPE", 1))
                                    printError("UNDEFINED SYMBOL");
				if(accept(Tokens.AMPERSAND))
                                    isPtr.add(Boolean.TRUE);
                                else
                                    isPtr.add(Boolean.FALSE);
				expect(Tokens.IDENT);
                                if(!symTable.add(new Const(Scanner.ident, "VAR", type, 1, "null")))
                                    printError("REDEFINED SYMBOL");
			} while (accept(Tokens.COMMA));
			tabCount--;
		}

	}

	private void type() {
		if (sym == Tokens.IDENT || sym == Tokens.INT || sym == Tokens.SHORT) {
			tabCount++;
                        if(sym == Tokens.IDENT)
                        {
                            type = Scanner.ident;
                            if(!symTable.isDefined(type, "TYPE", lexLevel))
                                printError("UNDEFINED SYMBOL");
                        }
                        else if(sym == Tokens.INT)
                            type = "int";
                        else if(sym == Tokens.SHORT)
                            type = "short";
			if (accept(Tokens.IDENT) || accept(Tokens.INT)
					|| accept(Tokens.SHORT)) {
				//printRule("type");
				tabCount--;
				return;
			}
		}

	}

        String retType = "";

	private void retType() {
		if (sym == Tokens.VOID || sym == Tokens.INT || sym == Tokens.IDENT
				|| sym == Tokens.SHORT) {
			tabCount++;
			//printRule("retType");
                        if(sym == Tokens.IDENT)
                        {
                            type = Scanner.ident;
                            if(!symTable.isDefined(type, "TYPE", 0))
                                    printError("UNDEFINED SYMBOL");
                        }
                        else
                        {
                            type = sym.lexeme.toLowerCase();
                        }
			if (accept(Tokens.VOID) || accept(Tokens.INT)
					|| accept(Tokens.IDENT) || accept(Tokens.SHORT))
				tabCount--;
                        retType = type;
			return;
		}

	}

	public void program() {
		if (sym == Tokens.MAIN || sym == Tokens.VOID || sym == Tokens.INT
				|| sym == Tokens.VAR || sym == Tokens.CONST
				|| sym == Tokens.GET || sym == Tokens.TYPE
				|| sym == Tokens.IDENT) {
			//printRule("program");
			declarations();
			procedureDeclarations();
			expect(Tokens.MAIN);
                        symTable.add(new ProcSym("MAIN", "void"));
                        lexLevel=1;
                        retType = "void";
			expect(Tokens.OPEN_PAREN);
			expect(Tokens.CLOSE_PAREN);
			expect(Tokens.OPEN_BRACE);
			declarations();
			statementSequence();
			expect(Tokens.CLOSE_BRACE);
                        clear();
                        symTable.popTopLexLevel();
			expect(Tokens.END_OF);
                        retType = "";
		} else
			printError(sym);
	}
	
	public static void processDataDecl()
	{
		for(Symbol s : symTable.table)
		{
			if(s.classification.equals("VAR"))
				codeGen.insertData(s.name + ":		.word 0");
			else if(s.classification.equals("CONST"))
				codeGen.insertData(s.name + ":		.word "+((Const)s).value);
		}
	}

	private void statementSequence() {
		if (sym == Tokens.IDENT || sym == Tokens.GET || sym == Tokens.PRINT
				|| sym == Tokens.WHILE || sym == Tokens.IF
				|| sym == Tokens.RETURN || sym == Tokens.COLON) {
			tabCount++;
			//printRule("statementSequence");
			do {
				statement();
			} while (sym == Tokens.IDENT || sym == Tokens.GET
					|| sym == Tokens.PRINT || sym == Tokens.WHILE
					|| sym == Tokens.IF || sym == Tokens.RETURN
					|| sym == Tokens.COLON);
			tabCount--;
		} else {
			printError(sym);
		}

	}

	private void statement() {
		if (sym == Tokens.IDENT || sym == Tokens.GET || sym == Tokens.PRINT
				|| sym == Tokens.WHILE || sym == Tokens.IF
				|| sym == Tokens.RETURN || sym == Tokens.COLON) {
			tabCount++;
			//printRule("statement");
			if (sym == Tokens.IDENT) {
				assignment();
                                //eType = "";
			} else if (sym == Tokens.GET) {
				input();
                                //eType = "";
			} else if (sym == Tokens.PRINT) {
				output();
                                //eType = "";
			} else if (sym == Tokens.WHILE) {
				whileStatement();
                               // eType = "";
			} else if (sym == Tokens.IF) {
				ifStatement();
                                //eType = "";
			} else if (sym == Tokens.RETURN) {
				returnStatement();
			} else if (sym == Tokens.COLON) {
				procedureStatement();
                               // eType = "";
			}
			tabCount--;
		}

	}

	private void procedureStatement() {
		tabCount++;
		//printRule("procedureStatement");
		procedureCall();
		expect(Tokens.SEMI_COLON);
		tabCount--;
	}

	private void returnStatement() {
		tabCount++;
		//printRule("returnStatement");
		expect(Tokens.RETURN);
		expression();
                compareTypes(retType, type);
                clear();
		expect(Tokens.SEMI_COLON);
		tabCount--;

	}

        public void compareTypes(String typeA, String typeB)
        {
            if(typeA.equals("short") && !typeB.equals("short"))
            {
                    printError("TYPE MISMATCH");
            }
            else if(typeA.equals("void") && !typeB.equals("void"))
            {
                printError("TYPE MISMATCH");
            }
            else if(typeA.equals("int") && typeB.equals("void"))
            {
                printError("TYPE MISMATCH");
            }
        }

	private void ifStatement() {
		tabCount++;
		//printRule("ifStatement");
		expect(Tokens.IF);
		condition();
		expect(Tokens.OPEN_BRACE);
		statementSequence();
		expect(Tokens.CLOSE_BRACE);
		if (accept(Tokens.ELSE)) {
			expect(Tokens.OPEN_BRACE);
			statementSequence();
			expect(Tokens.CLOSE_BRACE);
		}
		tabCount--;

	}

	private void whileStatement() {
		tabCount++;
		//printRule("whileStatement");
		expect(Tokens.WHILE);
		condition();
		expect(Tokens.OPEN_BRACE);
		statementSequence();
		expect(Tokens.CLOSE_BRACE);
		tabCount--;

	}

	private void condition() {
		tabCount++;
		//printRule("condition");
		expect(Tokens.OPEN_PAREN);
		expression();
		if (!(accept(Tokens.EQUAL) || accept(Tokens.NOT_EQ)
				|| accept(Tokens.LESS_THAN) || accept(Tokens.LESS_THAN_EQ)
				|| accept(Tokens.GRT_THAN) || accept(Tokens.GRT_THAN_EQ))) {
			printError(sym);
		}
		expression();
		expect(Tokens.CLOSE_PAREN);
		tabCount--;

	}

	private void output() {
		tabCount++;
		//printRule("output");
		expect(Tokens.PRINT);
		expect(Tokens.OPEN_PAREN);
                lastProc = "";
                origin = "PRINT";
		parameters();
		expect(Tokens.CLOSE_PAREN);
		expect(Tokens.SEMI_COLON);
		tabCount--;

	}

	private void input() {
		if (expect(Tokens.GET)) {
			tabCount++;
			//printRule("input");
			expect(Tokens.OPEN_PAREN);
                        lastProc = "";
                        origin = "GET";
			parameters();
			expect(Tokens.CLOSE_PAREN);
			expect(Tokens.SEMI_COLON);
			tabCount--;

		}

	}

	private void assignment() {
		if (sym == Tokens.IDENT) {
			tabCount++;
			//printRule("assignment");
			designator();
                        String aType = eType;
                        eType = "";
			expect(Tokens.ASSIGN_OP);
			expression();
			expect(Tokens.SEMI_COLON);
                        compareTypes(aType, eType);
			tabCount--;
		}
	}

        String expType = "";

	private void expression() {
		if (sym == Tokens.NUMBER || sym == Tokens.IDENT || sym == Tokens.COLON
				|| sym == Tokens.OPEN_PAREN) {
			tabCount++;
			//printRule("expression");
			do {
				term();
			} while (accept(Tokens.PLUS) || accept(Tokens.MINUS));

			tabCount--;
		}
	}

	private void term() {
		if (sym == Tokens.NUMBER || sym == Tokens.IDENT || sym == Tokens.COLON
				|| sym == Tokens.OPEN_PAREN) {
			tabCount++;
			//printRule("term");
			do {
				factor();
			} while (accept(Tokens.MULT_OP) || accept(Tokens.DIV));
			tabCount--;
		}

	}

	private void factor() {
		if (sym == Tokens.NUMBER || sym == Tokens.IDENT || sym == Tokens.COLON
				|| sym == Tokens.OPEN_PAREN) {
			tabCount++;
			//printRule("factor");
			if (sym == Tokens.IDENT) {
				designator();
				tabCount--;
				return;
			} else if (accept(Tokens.NUMBER)) {
                                if(!eType.equals("int"))
                                    eType = "short";
				tabCount--;
				return;
			} else if (sym == Tokens.COLON) {
				procedureCall();
				tabCount--;
				return;
			} else if (accept(Tokens.OPEN_PAREN)) {
				expression();
				expect(Tokens.CLOSE_PAREN);
				tabCount--;
				return;
			}
		}
	}

	private void procedureCall() {
		tabCount++;
		//printRule("procedureCall");
		expect(Tokens.COLON);
		expect(Tokens.COLON);
		expect(Tokens.IDENT);
                lastProc = Scanner.ident;
		expect(Tokens.OPEN_PAREN);
		parameters();
                lastProc = "";
		expect(Tokens.CLOSE_PAREN);
		tabCount--;

	}

        String eType = "";
        
	private void designator() {
            int arity = 0;
            int desigArity = 0;
            String desigName = "";
            if (sym == Tokens.IDENT) {
                    desigName = Scanner.ident;
                    tabCount++;
                    //printRule("designator");
                    expect(Tokens.IDENT);
                    
                    if (accept(Tokens.OPEN_BRKT)) {
                            do {
                                    arity++;
                                    expression();
                                    expect(Tokens.CLOSE_BRKT);
                            } while (accept(Tokens.OPEN_BRKT));
                    }
                    //Check Arity
                    Symbol s;
                    s = symTable.lookUp(desigName, "VAR");

                    if(s == null)
                        printError("UNDEFINED SYMBOL");
                    else
                    {
                        TypeSym t = (TypeSym)symTable.lookUp(s.type, "TYPE");
                        while(!t.type.equals("null")){
                            if(t.dim > 0)
                                desigArity++;
                            t = (TypeSym)symTable.lookUp(t.type, "TYPE");
                        }
                        if( arity != desigArity )
                            printError("ARITY ERROR");

                        if(!eType.equals("int") && lookupBaseType(s).equals("short")){
                            eType = "short";
                        }
                        else if(eType.equals("int") || lookupBaseType(s).equals("int"))
                        {
                            eType = "int";
                        }
                    }
                    tabCount--;
            }

	}


        String lastProc = "";
        String origin = "";
        
	private void parameters() {
		tabCount++;
		if (sym == Tokens.IDENT) {
                    if(lastProc.equals(""))
                    {
                        do {
                                    designator();
                                    if(symTable.lookUp(Scanner.ident, "VAR").classification.equals("CONST") && origin.equals("GET"))
                                        printError("TYPE MISMATCH");
                                    eType = "";
                                    
                            } while (accept(Tokens.COMMA));
                    }
                    else
                    {
                        //printRule("parameters");         
                        int i = 0;
                        do {
                            try
                            {
                                designator();
                                String identName = Scanner.ident;
                                String parType = lookupBaseType(symTable.lookUp(Scanner.ident, "VAR"));
                                
                                compareTypes(((ProcSym)symTable.lookUp(lastProc, "PROCEDURE")).paramTypes.get(i),
                                        parType);
                             
                                if(symTable.lookUp(identName, "CONST").classification.equals("CONST") &&
                                        ((ProcSym)symTable.lookUp(lastProc, "PROCEDURE")).isPointer.get(i))
                                    printError("TYPE MISMATCH");
                                if(symTable.lookUp(Scanner.ident, "VAR").classification.equals("CONST") && origin.equals("GET"))
                                    printError("TYPE MISMATCH");
                                eType = "";
                                i++;
                            }
                            catch(IndexOutOfBoundsException e)
                            {
                                i++;
                            }
                        } while (accept(Tokens.COMMA));

                        if(i != ((ProcSym)symTable.lookUp(lastProc, "PROCEDURE")).paramTypes.size())
                        {
                            printError("ARITY ERROR");
                        }
                    }
		}
                else if(((ProcSym)symTable.lookUp(lastProc, "PROCEDURE")).paramTypes.size() > 0)
                {
                    
                    printError("ARITY ERROR");
                }
                origin = "";
		tabCount--;

	}

	/**
	 * If you choose to use this design, you might want to look at
	 * Recursive_descent_parser in Wikipedia Although you are welcome to use any
	 * other design you can think of.
	 */
	private boolean accept(Tokens t) {
		if (sym == t) {
			sym = Scanner.getSym();
			return true;
		}
		return false;
	}

	private boolean expect(Tokens t) {
		if (accept(t))
			return true;
		printError(sym);
		return false;
	}

	public static void main(String[] args) {

		Parser p = new Parser(args);
                symTable = new SymbolTable();
                names = new ArrayList<String>();
		p.program();
        //printTable();
        codeGen.dumpProgram(fileData);
		try {
			outFile.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
 
}
